use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

use bevy_ecs::entity;

use deno_core::*;


use uuid::Uuid;

use crate::function::scene::components::IdComponent;
use crate::function::scene::entity::Entity;
use crate::function::scene::scene::Scene;
use crate::function::utils::io::AliceUtils;

use super::binding::JsBinding;

pub struct InitScriptInstanceInfo<'a> {
    holder: entity::Entity,
    code: String,
    class_name: &'a str,
    uid: Uuid,
}

pub struct ScriptInstance {
    pub holder: entity::Entity,
    pub runtime: JsRuntime,
    pub instance: v8::Global<v8::Value>,
}

impl ScriptInstance {
    pub fn new(init_info: InitScriptInstanceInfo) -> Self {
       
        // Initialize a runtime instance
        let mut runtime = JsRuntime::new(RuntimeOptions {
            extensions: vec![JsBinding::get_extension()],
            ..Default::default()
        });

        let js_code = format!(
            "{{
                {c}
                new {f}()
                }}",
            c = init_info.code,
            f = init_info.class_name
        );
        let res = runtime.execute_script("<anon>", &js_code);

        match res {
            Ok(global) => {
                {
                    let scope = &mut runtime.handle_scope();

                    let id      = v8::String::new(scope, &init_info.uid.to_string()).unwrap();
                    
                    
                    let holder = v8::Local::new(scope, global.clone());

                 
                    JsBinding::try_invoke_js_function(scope, holder, "onCreate", &[id.into()]);

                    // if local.is_object() {
                    //     let obj = local.to_object(scope).unwrap();
                    //     let create_cb_name = deno_core::v8::String::new(scope, "onCreate").unwrap();
                    //     let create_cb = obj.get(scope, create_cb_name.into()).unwrap();
                    //     if create_cb.is_function() {
                    //         let create_method = v8::Local::<v8::Function>::try_from(create_cb)
                    //             .expect("获取脚本 onCreate 方法失败");
                          
                    //         create_method.call(scope, obj.into(), &[id.into()]);
                    //     }
                    // }
                }

                return Self {
                    instance: global,
                    runtime,
                    holder: init_info.holder,
                };
            }

            Err(_) => panic!("get script instance failed! class name is {:?}",init_info.class_name),
        }
    }
}



#[derive(Clone)]
pub struct AliceScriptEngine(Rc<RefCell<AliceScriptEngineImpl>>);

impl AliceScriptEngine {
    pub fn new() -> Self {
        Self(Rc::new(RefCell::new(AliceScriptEngineImpl::default())))
    }
}

impl AliceScriptEngine {
    pub fn instance_script(
        &mut self,
        entity: Entity,
        scene: &Scene,
        script_path: &str,
        class_name: &str,
    ) {
        self.0.borrow_mut().instance_script(entity, scene, script_path, class_name)
    }

    pub fn invoke_update(&mut self , id:Uuid, dt:f64){
        let mut binding = self.0.borrow_mut();
        let instance = binding.get_instance(&id).unwrap();
        let scope = &mut instance.runtime.handle_scope();
                  
        let dt = deno_core::v8::Number::new(scope,dt);
        let holder = deno_core::v8::Local::new(scope, instance.instance.clone());

        JsBinding::try_invoke_js_function(scope, holder, "onUpdate", &[dt.into()]);
    }
}

impl std::ops::Deref for AliceScriptEngine {
    type Target = Rc<RefCell<AliceScriptEngineImpl>>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}


#[derive(Default)]
pub struct AliceScriptEngineImpl {
    instance_map: HashMap<Uuid, ScriptInstance>,
}

impl AliceScriptEngineImpl {
    pub fn instance_script(
        &mut self,
        entity: Entity,
        scene: &Scene,
        script_path: &str,
        class_name: &str,
    ) {
        let uid = entity.get_component::<IdComponent>(scene).unwrap();

        let code = AliceUtils::read_text_file_unwrap(script_path);
        match code {
            Ok(code) => {
                let info = InitScriptInstanceInfo {
                    holder: entity.handle,
                    code,
                    class_name,
                    uid: uid.id,
                };

                let instance = ScriptInstance::new(info);

                self.instance_map.insert(uid.id, instance);
            }
            Err(_) => todo!(),
        }
    }

    pub fn get_instance(&mut self , id:&Uuid) -> Option<&mut ScriptInstance> {
        self.instance_map.get_mut(&id)
    }
}
